﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace V32VM
{
    class Disassembler
    {
        Memory memory;

        public Disassembler(Memory memory)
        {
            this.memory = memory;
        }

        string getReg(uint index)
        {
            switch (index)
            {
                case 0: return "a";
                case 1: return "b";
                case 2: return "c";
                case 3: return "d";
                case 4: return "e";
                case 5: return "f";
                case 6: return "g";
                case 7: return "t";
                case 8: return "u";
                case 9: return "v";
                case 10: return "w";
                case 11: return "x";
                case 12: return "y";
                case 13: return "z";
                case 14: return "s";
                case 15: return "ip";
                case 16: return "sp";
                case 17: return "fl";
                default: throw new Exception("No register with id " + index + ".");
            }
        }
        /* for typeLeft,typeRight
         *   00: register
         *   01: constant
         *   02: [register]
         *   03: [constant]
         */
        const bool SIZE_WORD = true,
            SIZE_BYTE = false;
        const byte TYPE_REG = 0,
            TYPE_CONST = 1,
            TYPE_REG_INDIRECT = 2,
            TYPE_CONST_INDIRECT = 3;

        uint argSize(bool size, byte type, bool relative, bool relativeSize)
        {
            // register = 1 byte, constant = 4 bytes
            uint bytes = (type % 2 == 0) ? 1u : 4u;
            if (relative)
                bytes += (uint)((relativeSize == SIZE_BYTE) ? 1 : 4);
            return bytes;
        }
        string srcArg(uint ip, uint address, bool size, byte type, bool relative, bool relativeSize)
        {
            // address points to start of argument data
            // size = use word/byte from indirection
            // type = argument type
            // relativeSize = word/byte size of offset

            if (type >= 4)
                throw new Exception("Invalid argument type '" + type + "'.");

            string str = "";

            if (type % 2 == 0) // register/[register]
            {
                str = getReg(memory[ip, address]);
                address++;
            }
            else // constant/[constant]
            {
                // constants are always 4 bytes
                str = memory.words[ip, address].ToString();
                address += 4;
            }

            if (relative)
            {
                if (relativeSize == SIZE_WORD)
                {
                    uint offset = memory.words[ip, address];
                    int ioffset = unchecked((int)offset);
                    //str += "+" + offset.ToString();
                    if (ioffset >= 0)
                        str += "+" + ioffset.ToString();
                    else str += ioffset.ToString();
                    address += 4;
                }
                else
                {
                    str += "+" + memory[ip, address].ToString();
                    address++;
                }
            }

            if (type >= 2) // indirect ([register]/[constant])
            {
                if (size == SIZE_WORD)
                {
                    str = "[" + str + "]w";
                }
                else
                {
                    str = "[" + str + "]b";
                }
            }

            return str;
        }
        string destArg(uint ip, uint address, bool size, byte type, bool relative, bool relativeSize)
        {
            // address points to start of argument data
            // size = use word/byte from indirection
            // type = argument type
            // relativeSize = word/byte size of offset

            if (type >= 4 || type == 1) // 1 = constant; cannot set a constant to a value...
                throw new Exception("Invalid argument type '" + type + "'.");

            string str;

            if (type == 0) // register
            {
                str = getReg(memory[ip, address]);
            }
            else if (type == 2) // [register]
            {
                str = getReg(memory[ip, address]);
                address++;
                if (relative)
                {
                    if (relativeSize == SIZE_WORD)
                    {
                        uint offset = memory.words[ip, address];
                        int ioffset = unchecked((int)offset);
                        //str += "+" + offset.ToString();
                        if (ioffset >= 0)
                            str += "+" + ioffset.ToString();
                        else str += ioffset.ToString();
                        address += 4;
                    }
                    else
                    {
                        str += "+" + memory[ip, address].ToString();
                        address++;
                    }
                }
                if (size == SIZE_WORD)
                    str = "[" + str + "]w";
                else str = "[" + str + "]b";
            }
            else if (type == 3) // [constant]
            {
                str = memory.words[ip, address].ToString();
                address += 4;
                if (relative)
                {
                    if (relativeSize == SIZE_WORD)
                    {
                        uint offset = memory.words[ip, address];
                        int ioffset = unchecked((int)offset);
                        //str += "+" + offset.ToString();
                        if (ioffset >= 0)
                            str += "+" + ioffset.ToString();
                        else str += ioffset.ToString();
                        address += 4;
                    }
                    else
                    {
                        str += "+" + memory[ip, address].ToString();
                        address++;
                    }
                }
                if (size == SIZE_WORD)
                    str = "[" + str + "]w";
                else str = "[" + str + "]b";
            }
            else throw new Exception("We already talked about this...");

            return str;
        }

        public Tuple<uint, string>[] dissasemble(uint ip, int numInstructions)
        {
            /* NOTE: in the following, if a command does not have a certain argument,
             *   the bit/value relating to that argument is assumed to be 0
             * 
             * for byte 1:[6-7], "size" is only relevant for indirection.
             *   size = true -> use a whole word pointed to by the address
             *   size = false -> use only a single byte pointed to by the address
             * 
             * byte 1
             * ------
             * [0-5]: opcode
             * [6]: size of first operand (0=1 byte, 1=4 bytes)
             * [7]: size of second operand
             * 
             * byte 2
             * ------
             * [0-1]: arg1 type
             * [2-3]: arg2 type
             * [4]: arg1 relative
             * [5]: arg2 relative
             * [6]: arg1 offset size
             * [7]: arg2 offset size
             */

            List<Tuple<uint, string>> commands = new List<Tuple<uint, string>>();

            while (numInstructions-- > 0)
            {
                try
                {
                    uint curIP = ip;
                    string str;
                    byte instr0 = memory[ip, ip];
                    if (instr0 == 21)
                    {
                        str = "iret";
                    }
                    else
                    {
                        byte instr1 = memory[ip, ip + 1];

                        byte opcode = (byte)(instr0 & ((1 << 6) - 1));
                        bool sizeLeft = (instr0 & (1 << 6)) != 0;
                        bool sizeRight = (instr0 & (1 << 7)) != 0;

                        byte typeLeft = (byte)(instr1 & 3);
                        byte typeRight = (byte)((instr1 >> 2) & 3);
                        bool leftRelative = (instr1 & (1 << 4)) != 0;
                        bool rightRelative = (instr1 & (1 << 5)) != 0;
                        bool leftRelativeSize = (instr1 & (1 << 6)) != 0;
                        bool rightRelativeSize = (instr1 & (1 << 7)) != 0;

                        uint bytesLeft, bytesRight;
                        switch (opcode)
                        {
                            case 0:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "ld " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 1:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "add " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 2:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "sub " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 3:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "mul " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 4:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "div " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 5:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "mod " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 6:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "and " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 7:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "or " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 8:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "xor " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 9:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "lshift " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 10:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "rshift " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 11:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "inv " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 12:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "neg " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 13:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "clr " + destArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            // the following commands do not have dest-args (just a note)
                            case 14:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                bytesRight = argSize(sizeRight, typeRight, rightRelative, rightRelativeSize);
                                str = "cmp " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize)
                                    + " " + srcArg(ip, ip + 2 + bytesLeft, sizeRight, typeRight, rightRelative, rightRelativeSize);
                                ip += 2 + bytesLeft + bytesRight;
                                break;
                            case 15:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jmp " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 16:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "je " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 17:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jne " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 18:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jg " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 19:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jl " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 20:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jle " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 21:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "jge " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            case 22:
                                bytesLeft = argSize(sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                str = "int " + srcArg(ip, ip + 2, sizeLeft, typeLeft, leftRelative, leftRelativeSize);
                                ip += 2 + bytesLeft;
                                break;
                            default: throw new Exception("Invalid opcode '" + opcode + "'.");
                        }
                    }

                    commands.Add(new Tuple<uint, string>(curIP, str));
                }
                catch
                {
                    // don't even try to parse instructions after a problem.
                    // it probably means we're in a .datasect
                    break;
                }
            }
            return commands.ToArray();
        }
    }
}
